package drds.plus.sql_process.parser;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import drds.plus.common.model.ThreadLocalString;
import drds.plus.common.model.comparative.And;
import drds.plus.common.model.comparative.Comparative;
import drds.plus.common.model.comparative.Or;
import drds.plus.common.model.hint.*;
import drds.plus.common.properties.ConnectionProperties;
import drds.plus.common.thread_local.ThreadLocalMap;
import drds.plus.common.utils.TStringUtil;
import drds.plus.rule_engine.utils.ComparativeStringAnalyser;
import drds.tools.$;
import lombok.extern.slf4j.Slf4j;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 简单hint解析
 *
 * <pre>
 * 完整的例子：
 *
 *  \/*+drds({"type":"condition","vtab":"vtabxxx","columnToRuleColumnMap":[{"relation":"and","expression_list":["pk>4","pk:long<10"],"paramtype":"int"}],"extra":"{"ALLOW_TEMPORARY_TABLE"="TRUE"})*\/
 * 1. type取值是condition和direct
 * 2. vtab取值是rule规则中的逻辑表名
 * 3. params对应于rule规则中的分库字段的条件
 *    a. relation取值是and和or,可以不用，但是expr里面元素必须为一个。（即where pk>4）
 *    b. expr对应为分库条件
 *    c. paramtype对应分库子段类型
 * 4. extra取值是针对{@linkplain ConnectionProperties}中定义的扩展参数，比如ALLOW_TEMPORARY_TABLE=true代表开启临时表
 *
 * type为direct时
 *  \/*+drds({"type":"direct","vtab":"real_tab","data_node_id":"xxx_group","real_table_names":["real_tab_0","real_tab_1"]})*\/query * from real_tab;
 * 绕过解析器, 进行表名替换,然后在对应group ds上执行
 * </pre>
 */
@Slf4j
public class HintParser {

    public static final String hint_prefix = "/*+hint(";
    public static final String hint_end = ")*/";
    //
    public static final String data_node_hint_prefix = "/*+data_node({";
    public static final String group_hint_end = "})*/";
    //
    public static final String extra = "extra";
    //
    public static final String type = "type";
    public static final String type_condition = "condition";
    public static final String relation = "relation";
    //
    public static final String type_direct = "direct";
    public static final String data_node_id = "data_node_id";
    public static final String virtual_table_name = "virtual_table_name";
    public static final String real_table_names = "real_table_names";
    //
    public static final String parameter_type = "parameter_type";
    public static final String parameters = "parameters";
    public static final String or = "or";
    public static final String and = "and";
    public static final String expression_list = "expression_list";


    public static RouteCondition parse(String sql) {
        // 检查下thread local directlyRouteCondition
        RouteCondition routeCondition1 = getRouteRouteConditionFromThreadLocal(ThreadLocalString.ROUTE_CONDITION);
        if (routeCondition1 != null) {
            return routeCondition1;
        }
        routeCondition1 = getRouteRouteConditionFromThreadLocal(ThreadLocalString.DB_SELECTOR);
        if (routeCondition1 != null) {
            return routeCondition1;
        }
        //
        String hint = extractHint(sql);
        if ($.isNotNullAndNotEmpty(hint)) {
            try {
                JSONObject jsonObject = JSON.parseObject(hint);
                String type = jsonObject.getString(HintParser.type);
                if (type_direct.equalsIgnoreCase(type)) {
                    return decodeDirectlyRouteCondition(jsonObject);
                } else if (type_condition.equalsIgnoreCase(type)) {
                    return decodeCondition(jsonObject);
                } else {
                    return decodeExtra(jsonObject);
                }
            } catch (JSONException e) {
                log.error("convert   sql_process to RouteCondition faild,check the directlyRouteCondition string!", e);
                throw e;
            }

        }

        return null;
    }

    public static RouteCondition getRouteRouteConditionFromThreadLocal(String key) {
        RouteCondition routeCondition = (RouteCondition) ThreadLocalMap.get(key);
        if (routeCondition != null) {
            RouteType routeType = routeCondition.getRouteType();
            if (RouteType.flush_on_execute.equals(routeType)) {
                ThreadLocalMap.remove(key);
            }
        }

        return routeCondition;
    }

    //

    /**
     * 从sql中解出hint,并且将hint里面的?替换为参数的String形式
     */
    public static String extractHint(String sql) {
        String hint = TStringUtil.getBetween(sql, hint_prefix, hint_end);
        if (null == hint || "".endsWith(hint)) {
            return null;
        }
        return hint;
    }

    public static String extractDataNodeHintString(String sql) {
        String hint = TStringUtil.getBetween(sql, data_node_hint_prefix, group_hint_end);
        if (null == hint || "".endsWith(hint)) {
            return null;
        }

        return hint;
    }

    public static String removeHint(String originsql) {
        String sql = originsql;
        String hint = TStringUtil.getBetween(sql, hint_prefix, hint_end);
        if (null == hint || "".endsWith(hint)) {
            return originsql;
        }
        sql = TStringUtil.removeBetweenWithSplitor(sql, hint_prefix, hint_end);
        return sql;
    }

    private static String get(JSONObject jsonObject, String key) throws JSONException {
        if (!jsonObject.containsKey(key)) {
            return null;
        }
        String value = jsonObject.getString(key);
        if ($.isNullOrEmpty(value)) {
            return null;
        }
        return value;
    }

    private static void decodeExtra(RouteCondition routeCondition, JSONObject jsonObject) throws JSONException {
        String extra = get(jsonObject, HintParser.extra);
        if ($.isNotNullAndNotEmpty(extra)) {
            JSONObject jsonObject1 = JSON.parseObject(extra);
            routeCondition.getExtraCmds().putAll(jsonObject1);
        }
    }

    private static void decodeVirtualTableName(RouteCondition routeCondition, JSONObject jsonObject) throws JSONException {
        String virtualTableName = get(jsonObject, virtual_table_name);
        if (virtualTableName == null) {
            throw new NullPointerException("directlyRouteCondition contains no property 'virtualTableName'.");
        }
        routeCondition.setVirtualTableNamesString(virtualTableName);
    }

    //
    public static RouteCondition decodeDirectlyRouteCondition(JSONObject jsonObject) {
        DirectlyRouteCondition directlyRouteCondition = new DirectlyRouteCondition();
        decodeExtra(directlyRouteCondition, jsonObject);
        //
        String dataNodeId = get(jsonObject, HintParser.data_node_id);
        if (dataNodeId == null) {
            throw new RuntimeException("directlyRouteCondition contains no property 'data_node_id'.");
        }
        directlyRouteCondition.setDataNodeId(dataNodeId);
        //
        decodeVirtualTableName(directlyRouteCondition, jsonObject);
        //
        String realTableNamesString = get(jsonObject, real_table_names);
        if (realTableNamesString == null) {
            throw new NullPointerException("directlyRouteCondition contains no property 'real_table_names'.");
        }
        JSONArray jsonArray = JSON.parseArray(realTableNamesString);
        // 设置table的Set<String>
        if (jsonArray.size() >= 0) {
            throw new NullPointerException("directlyRouteCondition contains  property 'real_table_names' no data");
        }
        Set<String> tableNameSet = new HashSet<String>(jsonArray.size());
        for (int i = 0; i < jsonArray.size(); i++) {
            tableNameSet.add(jsonArray.getString(i));
        }
        directlyRouteCondition.setRealTableNamesStringSet(tableNameSet);
        return directlyRouteCondition;
    }


    public static RouteCondition decodeCondition(JSONObject jsonObject) {
        RuleRouteCondition ruleRouteCondition = new RuleRouteCondition();
        decodeExtra(ruleRouteCondition, jsonObject);
        //
        decodeVirtualTableName(ruleRouteCondition, jsonObject);
        String parametersString = get(jsonObject, parameters);
        if (parametersString == null) {
            throw new NullPointerException("directlyRouteCondition contains no property 'parameters'.");
        }
        JSONArray parameters = JSON.parseArray(parametersString);
        if (parameters == null) {
            throw new NullPointerException("directlyRouteCondition contains  property 'parameters' no data");
        }
        for (int i = 0; i < parameters.size(); i++) {
            JSONObject parameter = parameters.getJSONObject(i);
            String parameterType = parameter.getString(HintParser.parameter_type);
            JSONArray expressionList = parameter.getJSONArray(HintParser.expression_list);
            if (parameter.containsKey(relation)) {
                String relation = parameter.getString(HintParser.relation);
                drds.plus.common.model.comparative.List list = null;
                if (relation != null && and.equals(relation)) {
                    list = new And();
                } else if (relation != null && or.equals(relation)) {
                    list = new Or();
                } else {
                    throw new SqlParserException("multi param but no relation,the directlyRouteCondition is:" + ruleRouteCondition.toString());
                }
                String key = null;
                for (int j = 0; j < expressionList.size(); j++) {
                    Comparative comparative = ComparativeStringAnalyser.decodeComparative(expressionList.getString(j), parameterType);
                    list.addComparative(comparative);
                    String $ = ComparativeStringAnalyser.decodeComparativeKey(expressionList.getString(j));
                    if (null == key) {
                        key = $;
                    } else if (!$.equals(key)) {
                        throw new SqlParserException("decodeCondition not support one relation with multi columnName,the relation is:[" + relation + "],expression_list comparativeList is:[" + expressionList.toString());
                    }
                }
                ruleRouteCondition.put(key, list);
            } else {
                if (expressionList.size() == 1) {
                    String key = ComparativeStringAnalyser.decodeComparativeKey(expressionList.getString(0));
                    Comparative comparative = ComparativeStringAnalyser.decodeComparative(expressionList.getString(0), parameterType);
                    ruleRouteCondition.put(key, comparative);
                } else {
                    throw new SqlParserException("relation neither 'and' nor 'or',but expression_list size is not 1");
                }
            }
        }
        return ruleRouteCondition;
    }

    private static ExtraCmdRouteCondition decodeExtra(JSONObject jsonObject) throws JSONException {
        ExtraCmdRouteCondition extraCmdRouteCondition = new ExtraCmdRouteCondition();
        String extra = get(jsonObject, HintParser.extra);
        if ($.isNotNullAndNotEmpty(extra)) {
            JSONObject jsonObject1 = JSON.parseObject(extra);
            for (Map.Entry<String, Object> entry : jsonObject1.entrySet()) {
                extraCmdRouteCondition.getExtraCmds().put(entry.getKey().toLowerCase(), entry.getValue());
            }
        }
        return extraCmdRouteCondition;
    }


}
